查看原文
其他

做嵌入式,如何优雅地debug?

罗小p RoboMaster 2019-08-09

Bug,是码农的眼中刺。在古老的电子管计算机年代,赫柏抓住人类历史上的第一只bug后,码农和bug开展了几十年的斗争。


鲁迅(没)说过:“赛场如战场,bug满天飞。”


平时1%才发生的bug,比赛时就200%地出现了,电控人员肩负着debug的重任。

 

嵌入式眼中的赛场


然而,人类就喜欢作死,平时放任bug不管,事后转发锦鲤祈祷。


某不愿意透露姓名的RoboMaster工程师实在看不下去,气得写了一篇嵌入式方法论。它不会手把手教你写代码,但是会告诉你如何避免写bug。


建议朋友们敲bug代码前看一看。毕竟,天道好轮回,该背锅时就背锅。


 本期作者 


RoboMaster嵌入式工程师 罗小p

不管你们说什么,XX才是世界上最好的语言!



嵌入式是啥?


专业一点说,嵌入式系统(Embedded System)是一种嵌入机械或电气系统内部、具有专一功能和实时计算性能的计算机系统。


而说人话,嵌入式就是把代码写进单片机里,再把单片机塞进一堆冷冰冰的机械中,代码就能控制机械动起来。

 

电路板


我们要学的,就是如何写出最顺滑、不卡机的代码。


机器人上的嵌入式技术



嵌入式一般用c语言编程,只需要上手c语言,就可以玩单片机啦。


用了单片机,是不是还要设计电路板?不不不,我们只要看懂原理图的管脚接入就够了,设计的事就丢给硬件的队友吧。 

 


在机器人身上,有哪些嵌入式需要做的事呢?


比如步兵,最简单的是LED控制,让LED灯亮和灭。


接着就是串口通信,这是最常见的通信方式,就像一根电话线,各部分数据用它来通信。


串口的DMA用在遥控器的接收,遥控器能不能控制机器人,就看它了!


DMA工作流程


再比如传感器,电流传感器会用ADC模拟量,掌握了ADC采样原理也会方便开发。


还有PWM,它的输出可以控制摩擦轮的转速,调节枪口射击的速度。


TIM定时器用于常用的定时任务,可用于任务分配。比如爪子倾倒弹药箱后,停顿0.5秒等弹丸掉完后再放回箱子。


英雄取弹

 

如果要控制官方产品(例如电机),就会用到通信总线CAN。如果它崩了,电机就疯了。


SPI通信板搭载陀螺仪,可以采集角速度值;CRC校验可以用在裁判系统的通信。


创建和调配任务,还能用freeRTOS(一种操作系统)


嵌入式知识框架


假设你看懂了以上知识,那么你已经成为半个嵌入式人了,我们将进入下一个环节——收集需求。


如果你没看懂,不要方,因为小R也没看懂,还是骄傲地进入了下一环节。


1. 确定需求 



磨刀不误砍柴工,在下手之前,要确定好需求,也就是搞清楚机器人想怎么动。


比如英雄机器人登岛取弹,得知道登岛取弹机构的运动顺序。


首先打开爪子→开到柱子旁→云台看屏幕→按键关闭爪子→机器人自动抬升→打开涵道等等……最后下岛过程反向即可 。


英雄登岛取弹流程图

 

而且,写代码不是一个人的事。自从开始写代码,我的头不秃了,眼睛不瞎了,人际关系也变好了。


昨晚,操作手就跪着把需求递给我,让我帮他实现:按右键开摩擦轮,按左键发射,按WSAD键就能前后左右跑。


不过得记着,没确定的按键要用宏定义,方便以后操作手再跪着找你改需求。


比赛遥控器



作为高贵的嵌入式高级的搬砖工,我们不在乎爪子的机械结构,只关心爪子的电机怎么转。


所以,还要给机械队友讲电机的类型和使用范围,让他们去算力矩校核;还得普及传感器的精度和用法,才能合作选出最适合的电机。


毕竟,他们啥都不懂,唉。


轮子上用的电机

 

硬件是嵌入式的战友,一般用来甩锅,只需要沟通电子元件的布置和外部接口。


根据以上,可以列出hin多软件需求。以下不要求背诵,可快速浏览。


1. 主要功能的需求,比如奔跑、取弹和射击,包括:

 

a. 触发功能的各种条件(如:控制流,运行状态,运行模式)。

b. 定义各种条件下所有可能的输入(包括合法的输入和非法的输入)。

c. 各个功能间可能的相互关系。

d. 功能性的主要级别(如:基本功能、逐步实现的功能,和可变化的功能)。

e. 落实待定功能的实现时间、负责人员,和内容说明。


2. 辅助性功能的需求,也就是可有可无,但有了会更好的需求。比如上位机发送数据到PC端,会存在数据延迟。如果列出数据,就可以对比两边的数据准确度。


a. 描述输入和输出的关系。

b. 描述输入和输出,其编号和名称必须唯一。


3. 软件和硬件,或者其他外部系统接口的需求:

 

a. 硬件接口:端口号,指令集,输入输出的内容,初始化过程,通道号和信号处理方式。

b. 软件接口。

c. 通讯接口 :指定通讯接口和通讯协议等。 


4. 程序上的非功能性需求:


a. 时间性能指标:软件处理、功能恢复,和输入响应的时间。 

b. 精度性能指标:软件处理ADC的精度,控制的精度等。

c. 稳定性指标:也就是程序遇到故障时,能有多稳。比如遥控器掉线时,如果有掉线保护,机器人就不会失控。


5. 程序不能做什么。例如“遥控器按关闭,机器人就必须停止”的代码应该独立于其他代码,不然其他代码一崩,机器人就不受控了。


2.  程序编写



确定完需求,终于到了敲代码环节。


这是嵌入式人最隆重、最端庄的仪式,需要遵守格式的规则。万一稍不小心触犯的某条,会有损程序员的格调。


写代码时,风格要统一。别上面一段是清新简约风,空格只打2下,下面就粗犷豪迈风,空格狂敲5、6、7、8下。


不仅影响阅读心情,不利后期维护,还容易危及自己的生命安全,被原地暴打。


下面举几个我对自己的要求:


1. 命令方式统一

常见的命名方式有帕克斯命名法,驼峰命名法和匈牙利命名法等。


哪种方法都可以,但是作为一名嵌入式人,我们只选一种,绝不花心。


例如机器人运行循环函数,它们分别长这样——


驼峰命名法 :robotRunLoop

帕斯卡命名法:RobotRunLoop

匈牙利命名法:fnRobotRunLoop(其中fn代表function 函数的意思)

下划线命名法:robot_run_loop


选一个你喜欢的,用就是了。


2. 命名准确无歧义


命名应该准确表达,别支支吾吾的。


例如计数——


错误示例: int i,a,b,c…

正确示例: int temp_count;(temp可以代表局部变量)


如果是具体含义的计数,比如底盘电机的计数,那就是:

int tempChassisMotorCount;

如果计数是为了求数组的最大值,那就是:

int countForBufMax;


3. 排版整齐


 a. 适当加空行,能逻辑分割代码。

 b.  缩进不要混用制表符和空格,因为编辑器打开它们时,显示的缩进量不一样。

 c. 太长的语句,合理换行。


格式统一

格式不统一 


4. 定期检查代码


好马不吃回头草,优秀的嵌入式人才不吃这一套。


我们都会定期检查代码,及时debug,以免太久远的代码被遗忘,改起来太费劲。毕竟,贵人多忘事啊。


编程规范可参考《华为技术有限公司c语言编程规范》来制定。网上搜一下就能下载。


3. 风险同步



ddl是第一生产力,我们要预估任务时间,给自己定一个每日小目标,在ddl来临之时能够收工回家。


 

计划很重要,因为优秀的嵌入式人是需要合作的,你不知道队友会不会给你挖出个坑来。


比如调试哨兵是机械、嵌入式和视觉一起完成的。机械设计结构和装配,完成后,嵌入式让它动起来、控制云台和射击,最后视觉调试自动识别功能。

 

用装甲板测试识别功能



如果机械设计不合理,导致嵌入式调试出bug,机械背锅!如果嵌入式参数有问题,导致机械撞墙损伤,嵌入式背锅!


万一信息不通畅,导致机械做完后嵌入式还在外花天酒地,一起背锅!(视觉表示:反正我不背锅。)

 



最后的结果是,整体进度被延迟,谁都逃不掉锅。


为了避免成为背锅侠,我用血泪史总结出了常见的风险类型: 


机械背锅侠请点击看:《机械方法论

 


1. 需求风险:在前期要测试部分模块(比如一个爪子),讲究快速验证;中期调试整台机器,实现功能的稳定;后期配合队友调试视觉。


在整个项目,嵌入式要化身多种角色,完成各种需求。


2. 人员风险:有时会遇到人员不足的情况,我相信优秀的嵌入式人靠个人魅力也能把他们骗回来。


3. 时间风险:学生嘛,课业很多,希望你在进度完成的同时,也不要挂科。


4. 技术风险:优秀的我们也会偶尔犯错,由于代码设计不合理,平时不注意维护上,机器人有可能一上场就疯了。一定要提前发现问题,找到解决方案。


5. 备料风险:曾有人不小心烧掉电路板,还没有备用板,差点在赛场原地痛哭。


如果你放不下颜面当众大哭,一定要备好货,常坏的零件更要囤多点。

 

备用零件


不过有时,你跑得再快,ddl还是追上了你。


不要方,项目延期很正常,我们应该大声地同步风险,避免坑了队友。因为,嵌入式人永不认输。


 


4. 代码测试



现在,到了隆重的测试程序环节。是驴是马,牵出来遛一遛就知道了。


作为优秀的嵌入式人,需要有条理地、系统地准备测试。


假设要测步兵的功率控制,就要列出—— 


测试目的:机器人在模拟场上运行,保证底盘功率不超限制。

测试对象:步兵的底盘功率控制策略。

测试人员:了解步兵操作、裁判系统设置的人。

测试时间:排除太过寒冷的气候。因为电池放电受到温度的影响。

精度要求:场地的沙地纹地胶厚度3mm,坡度最大17°,还要考虑实际使用时,机器人会先跑一会再冲上通过斜坡。


测试还要考虑机器人的使用环境。不管它是在-10℃的水里潜伏,还是在80℃的沙滩上狂奔,都要尽量满足实际的工况。


像传感器的数据会受温度影响。如果机器人在冬天的哈尔滨测好,拿到夏天的深圳可能就崩了。 


赛场上检测机器人


测试分成静态测试和动态测试,一般是先静态,后动态。


1. 静态测试就是,我看看代码,但我不运行。通过肉眼观察代码、界面和文档,来找出错误。静态也有两种方法:


a. 队友互相打脸纠错,不仅可以看别人代码的组成,还能为别人debug。


b. 如果不放心队友,还可以用静态代码检测工具。历经千辛万苦,我终于找到了一个免费工具:cppcheck。


cppcheck界面


2. 动态调试就是,输入测试数据,看代码跑得顺不顺。


一般有下面几种方法:


a. 监控任务:监控电机、传感器等模块的工作状态。比如发现电机疯转就赶紧关停。


b. 心跳任务:看LED、OLED屏等显示设备,来判断工作状态。比如看到红灯闪烁8下,就是第8个电机有问题。


电路板测试

 

c. 错误码显示:用LED灯、显示屏、串口等显示错误码。还是上一张图,假如第6个绿灯变红,就说明第6个电机有问题。



d. 运用调试工具:比如J-link调试器可以使用J-Scope工具来完成绘图(而且软件是完全免费的)


J-Scope


甚至还可以用来调试云台的PID。



绿线是 pitch的角速度设定值

黄线是pitch角速度反馈值

紫线是pitch的角度设定值

蓝线是pitch的角度反馈值


熟练使用各种工具简直可以事半功倍,是优秀嵌入式人居家旅行的必备良药!


测完后,你就会发现,想象中是完美的,运行中是崩溃的。当初脑子是进了多少水,才会写出如此反人类的代码。 


5. 代码需要持续优化



优秀的嵌入式人会不断地完善代码,这让程序更加高效,功能结构更健全。


如何优化代码?首先要了解程序的性能,再找到不合理的地方来优化。


比如陀螺仪的通信,常用SPI等待读取。但是在CPU在读取时,需要等待通信完成,这是个无意义等待,如果用SPI的DMA方式就可以节约时间。


还应该了解代码的函数运行。对于函数用了多少次除法、乘法运算、调用了多少次、是否存在函数递归等等,心里要有数。


之后再优化那些调用次数较多,或者使用除乘法较多。

 

生活在于不断学习


机器人的程序一般没有复杂的运算和处理,CPU不会满足不了计算要求。如果哪天发现程序跑不通了,请不要怪CPU,先从自己身上找问题。 


在RoboMaster论坛,有官方和参赛队的开源程序。有事没事看看别人的代码,你会发现,同样的功能竟然有各种方法实现。

 


比如PID调试,下图代码虽然短,但是每用一次就要粘贴一遍,整个程序会非常长。


 

而下图是一套PID结构体,想用的时候,改改参数就可以,整个程序就会干净整洁。



6. 阶段性胜利



   这时,希望你的计划都已经完成,作为阶段的结束,可以去聚餐庆祝啦需要复盘一下工作。毕竟,我们可是要做优秀的嵌入式工程师啊!


首先反思错误,把代码问题、延期原因记录下,避免下次再犯。


然后回顾工作,把完成的任务、画过的软件框架图,和代码的介绍写成文档,为后人种树。


作为优秀工程师,写技术文档也很严格,要包括:


1. 需求说明要点:例如机械设计需求、机器人性能需求,和底盘控制需求。

2. 操作使用、维护的要点:例如遥控器上和键盘的操作,机器人如何维护。

3. 硬件结构要点:例如IO口分配,硬件接线图。

4. 软件设计框架和流程图:例如软件架构、任务分配、任务功能,和内存分配。

5. 核心代码讲解。

6. 软件测试报告。



7. 嵌入式大礼包



最后再安利一些工具和开源代码,希望未来的嵌入式工程师们能够飞速成长,等哪天有缘相遇了,还望轻虐。 


RoboMaster 2016步兵开源代码:

https://www.robomaster.com/zh-CN/resource/download

RoboMaster电控、视觉算法开源代码:
https://github.com/Robomaster

Git:用于代码托管,方便版本迭代回溯。

Vscode:常用的代码编辑,调试软件,可以在上面进行代码编写。

Beyongcompare:文本比较,用于不同版本的代码文本之间对比。

Xmind:思维导图,帮助思维发散以及记录要点。

Staruml:画图软件,用于流程图等绘制。



    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存